package com.lagou.edu.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.jwt.crypto.sign.MacSigner;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.client.JdbcClientDetailsService;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;

import javax.sql.DataSource;

/**
 * @author renpeiqian
 * oauth2配置类
 * @date 2021/7/19 17:02
 * @since
 */
@Configuration
@EnableAuthorizationServer //开启认证服务器功能
public class OauthServerConfig extends AuthorizationServerConfigurerAdapter {

    //签名密钥
    private String SIGN_KEY = "Hik123";

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private  DataSource dataSource;

    /**
     * @Description ：
     * 认证服务器最终是以api接口的方式提供对外服务（开放校验合法性并生成令牌、校验令牌等接口）
     * 所以在此处配置访问api接口的权限
     * @Param [security]
     * @return void
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        super.configure(security);
        // 相当于打开endpoints 访问接⼝的开关，这样的话后期我们能够访问该接⼝
        security
                //允许客户端表单认证
                .allowFormAuthenticationForClients()
                //开启端口/oauth/token_key的访问权限（允许）
                .tokenKeyAccess("permitAll()")
                //开启端口/oauth/check_token的访问权限（允许）
                .checkTokenAccess("permitAll()");

    }

    /**
     * @Description
     * 客户端：连接到认证服务的其他服务
     * 用来配置客户端详情服务，客户端详情信息在这进行初始化。
     * 信息可以写死或者从数据库读取
     * @Param [clients] 
     * @return void
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        super.configure(clients);
//        clients
//                //客户端信息存储在什么地方，可以在内存中，也可以在数据库。
//                .inMemory()
//                //添加client配置，指定其client_id
//                .withClient("client_lagou")
//                //指定客户端的密码
//                .secret("123456")
//                //配置客户端能访问资源id清单
//                //此处得资源id需要和资源服务器的配置一致。
//                .resourceIds("autodeliver")
//                //认证类型/令牌颁发模式，可以配置多个在这里，但不一定都有。具体使用模式办法token，取决于客户端调用的时候携带的参数。
//                .authorizedGrantTypes("password","refresh_token")
//                //客户端的权限范围，此处配置为all全部即可
//                .scopes("all");

        //从数据源中读取客户端详情信息
        clients.withClientDetails(getJdbcClientDetailsService());


    }

    /**
     * @Description 配置数据源,从数据库固定表读取客户端详情
     * @Param []
     * @return org.springframework.security.oauth2.provider.client.JdbcClientDetailsService
     */
    @Bean
    public JdbcClientDetailsService getJdbcClientDetailsService(){
        JdbcClientDetailsService jdbcClientDetailsService =new JdbcClientDetailsService(dataSource);
        return jdbcClientDetailsService;
    }

    /**
     * @Description
     * 认证服务器是依据token来进行验证。
     * 此处配置token的细节（访问端点、存储方式、令牌服务（配置有效期等））
     * @Param [endpoints]
     * @return void
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        super.configure(endpoints);
        endpoints
                //指定token的存储方式
                .tokenStore(tokenStore())
                // token服务的⼀个描述 ，token生成细节的描述。
                .tokenServices(authorizationServerTokenServices())
                //配置认证管理器
                .authenticationManager(authenticationManager)
                //配置endpoint的http请求方式。
                .allowedTokenEndpointRequestMethods(HttpMethod.GET,HttpMethod.POST);
    }


    /**
     * @Description :配置token的存储方式
     * InMemoryTokenStore ：存储在内存中，方便开发调试。
     * JdbcTokenStore ：基于JDBC的实现版本，令牌（Token）会被保存在数据库中。
     * JwtTokenStore ：将令牌相关的数据进⾏编码，后端服务不需要存储。
     * @Param
     * @return
     */
     public TokenStore tokenStore(){

         //return  new InMemoryTokenStore();

         //使用jwt进行存储令牌
         return new JwtTokenStore(jwtAccessTokenConverter());
     }


     /**
      * @Description: jwt令牌转换器
      * 可以再次将secret签名密钥进行配置。
      * @Param []
      * @return org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter
      */
     public JwtAccessTokenConverter jwtAccessTokenConverter(){
         JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
         //配置签名密钥
         jwtAccessTokenConverter.setSigningKey(SIGN_KEY);

         //验证时使用的密钥，和签名密钥保持一致
         jwtAccessTokenConverter.setVerifier(new MacSigner(SIGN_KEY));

         return jwtAccessTokenConverter;

     }

     /**
      * @Description :返回token服务对象，该对象描述了token的细节信息。
      * @Param []
      * @return org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices
      */
     public AuthorizationServerTokenServices authorizationServerTokenServices(){
         //默认实现
         DefaultTokenServices defaultTokenServices = new DefaultTokenServices();

         //针对jwt令牌的添加
         defaultTokenServices.setTokenEnhancer(jwtAccessTokenConverter());

         //开启令牌刷新
         defaultTokenServices.setSupportRefreshToken(true);
         defaultTokenServices.setTokenStore(tokenStore());
         //设置access_token的有效期为20s （一般为两个小时，此处为了测试）
         defaultTokenServices.setAccessTokenValiditySeconds(20);
         //设置刷新令牌有效期为3天（刷新令牌是在access_token失效后，通过刷新令牌可以重新从认证服务器获取令牌。）
         defaultTokenServices.setRefreshTokenValiditySeconds(259000);

         return defaultTokenServices;
     }
}
